//-------------------------------------------------------------------------------------------------
//  obb.hpp - Definition of obb (oriented bounding box) for physics operations
//  (c) 2009 Simon Miessler
//-------------------------------------------------------------------------------------------------

#pragma once

#include <boost/array.hpp>
#include <boost/foreach.hpp>
#include <boost/optional.hpp>

#include "math_exports.hpp"
#include "matrix4x4.hpp"
#include "geometry_fwd.hpp"
#include "intersectable.hpp"
#include "raycastable.hpp"




namespace math
{
	/**
	 * Oriented Bounding Box in 3d space.
	 */
	template <typename T>
	class obb
		: public intersectable< obb<T> >
		, public raycastable< obb<T>, T >
	{
	public:

		/// The used floating-point type
		typedef T              float_t;

	public:

		/// Position of the bounding box (its center)
		vector3<T>     position;

		/// Boundaries of the box, the length of each edge on the corresponding axis
		vector3<T>     bounds;

		/// Orientation of the box
		matrix3x3<T>   rotation;

	public:

		/**
		 * Create an oriented bounding box.
		 * It is located at (0,0,0) with boundaries of (1,1,1).
		 */
		obb()
			/*position is set to (0,0,0) per default*/
			: position(0, 0, 0)
			, bounds(1, 1, 1)
			/*rotation is set to an identity matrix per default*/
		{}

		/**
		 * Create an oriented bounding box at the specified position, with the specified boundaries
		 * and rotation.
		 *
		 * @param position   the position of the box in world space
		 * @param bounds     half the length of each edge of the box
		 * @param rotation   the rotation of the box in world space
		 */
		obb(const vector3<T>& position, const vector3<T>& bounds, const matrix3x3<T>& rotation)
			: position(position)
			, bounds(bounds)
			, rotation(rotation)
		{
			assert(bounds.x >= 0);
			assert(bounds.y >= 0);
			assert(bounds.z >= 0);
		}


		/**
		 * Get an AABB in object space of this OBB.
		 * It is thus located at the origin (triple zero)
		 */
		aabb<T> toAABB() const
		{
			return aabb<T>(vector3<T>(0, 0, 0), bounds);
		}


		/**
		 * Get a transformation matrix that transform a vector from
		 * object (of the OBB) to world space.
		 */
		matrix4x4<T> transform() const
		{
			return matrix4x4<T>(rotation, position);
		}


		/**
		 * Get a list of all 6 normals of the 6 faces of this OBB.
		 * The normals are transformed in world space.
		 *
		 * @info   To get the normals in object space,
		 *         call aabb().normals() on this OBB.
		 */
		boost::array<vector3<T>,6> getNormals() const
		{
			// Obtain the normals of a normal aabb box and
			// transform them in world space.
			// This would be a great demonstration of SIMD
			boost::array<vector3<T>,6> normals = toAABB().getNormals();
			typedef vector3<T> vector;
			BOOST_FOREACH(vector& normal, normals)
			{
				normal = rotation.transform(normal);
			}

			return normals;
		}

		/**
		 * Get a list of all 6 planes that form the OBB.
		 * The planes are transformed in world space.
		 *
		 * @info   To get the planes in object space,
		 *         call aabb().planes() on this OBB.
		 */
		boost::array<plane<T>,6> getPlanes() const
		{
			boost::array<plane<T>,6> planes  = toAABB().getPlanes();

			matrix4x4<T> object_to_world = transform();

			// The planes are defined in object space (at the origin).
			// All we need to do is to transform them to world space

			typedef plane<T>   plane;
			BOOST_FOREACH(plane& plane, planes)
			{
				plane.position = object_to_world.transform(plane.position);
				plane.normal   = rotation.transform(plane.normal);
			}

			return planes;
		}




		/**
		 * Get a bounding sphere that defines the closest
		 * sphere for this OBB.
		 * The sphere is defined in world coordinates.
		 */
		sphere<T> boundingSphere() const
		{
			return sphere<T>(position, bounds.length());
		}
	};
	///////////////////////////////////////////////////////////////////////////////////////////////





	/**
	 * Transform an OBB by a 4x4 matrix.
	 *
	 * @return   the transformed OBB
	 */
	template <typename T>
	obb<T> transform(const obb<T>& obb_, const matrix4x4<T>& matrix)
	{
		return obb<T>(matrix.transform(obb_.position), obb_.bounds, matrix.rotation().transform(obb_.rotation));
	}
	///////////////////////////////////////////////////////////////////////////////////////////////




	/**
	 * In order to compile a binary that actually contains the code for our OBB,
	 * we have to explicitly instantiate the template OBBs and export those:
	 */
	template struct MATH_API intersectable< obb<float> >;
	template struct MATH_API intersectable< obb<double> >;
	template struct MATH_API raycastable< obb<float>, float>;
	template struct MATH_API raycastable< obb<double>, double>;
	template class MATH_API obb<float>;
	template class MATH_API obb<double>;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
